﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;

using QuickTuner.Properties;
using QuickTuner.Util;

namespace QuickTuner
{
    public partial class radioForm : Form
    {
        // class to help store validity of states across multiple threads that updates the UI
        protected class ValidAsyncState
        {
            public ValidAsyncState() { IsStillValid = true; }
            public bool IsStillValid { get; set; }
        }

        private readonly Radio _radio; // the Radio engine

        private readonly List<ToolStripMenuItem> _menuItems; // List of menu items for later ref.
        private ToolStripMenuItem _selectedMenuItem; // track the currently checked menu item

        private ValidAsyncState _lastState; // Track background UI thread validity

        private bool _isDragging; // Track form dragging
        private Point _lastMouseLocation; // Track mouse for form-dragging

        private double _targetOpactiy; // Opacity level tracking for animation
        private bool _suppressAnimation; // Flag for supressing animation

        // animation constants
        private const double OpactiyAnimationFactor = 0.2;
        private const double OpacityAnimationThreshold = 0.05;
        private const double ActiveOpacity = 1.0;
        private const double InactiveOpacity = 0.5;

        // save dialog references
        private editForm _editForm;

        // constructors _________________________________________________________________

        public radioForm(Radio theRadio)
        {
            InitializeComponent();

            this.Icon = Resources.appIcon;
            trayIcon.Icon = Resources.appIcon;


            // attach to the radio engine
            _radio = theRadio;
            _radio.CurrentStationChanged += radio_CurrentStationChanged;
            _radio.PlayStateChanged += radio_PlayStateChnged;

            // bogus state to avoid null checks
            _lastState = new ValidAsyncState();

            // menu items cache
            _menuItems = new List<ToolStripMenuItem>();

            // update UI to match current radio state
            RefreshRadioState();


            // setup animation parameters

            // it's likely that the mouse is not directly on the form
            // when the form starts, so we open the form with inactive mode
            this.Opacity = _targetOpactiy = InactiveOpacity;
        }

        // UI update routines ___________________________________________________________

        public void RefreshRadioState()
        {
            RefreshPlayState();
            RefreshCurrentStation();
            RefreshStationList();

            if (_editForm != null)
                _editForm.RefreshRadioState();
        }

        public void RefreshStationList()
        {
            // remove selected item, if any
            if (_selectedMenuItem != null) {
                _selectedMenuItem.Checked = false;
                _selectedMenuItem = null;
            }

            // remove all cached items
            _menuItems.ForEach(item => stationsListMenu.Items.Remove(item));
            _menuItems.Clear();

            // rebuild menu items
            _menuItems.AddRange(_radio.Stations.Select(station => {
                var menuItem = new ToolStripMenuItem { Text = station.Title };

                // download icon in the background
                IconCache.BeginGetIcon(station, ar => {
                    var img = IconCache.EndGetIcon(ar);
                    if (img != null) setToolStripImage(menuItem, img);
                });

                menuItem.Tag = station;

                if (station == _radio.CurrentStation)
                    (_selectedMenuItem = menuItem).Checked = true;

                menuItem.Click += stationItem_Click;
                return menuItem;
            }));

            // add them back to the drop down
            // index is required so items are inserted above any existing menus there
            var index = 0;
            _menuItems.ForEach(item => stationsListMenu.Items.Insert(index++, item));
        }

        public void RefreshCurrentStation()
        {
            var currentStation = _radio.CurrentStation;

            // if no station is available for current station,
            // instruct the user to add some
            if (currentStation == null) {
                stationsListDropDown.Text = "Add some stations.";
                setToolStripImage(stationsListDropDown, Resources.musicImage);

                return;
            }

            stationsListDropDown.Text = currentStation.Title;


            // invalidate last download session if its still running
            _lastState.IsStillValid = false;

            // create a new state flag
            _lastState = new ValidAsyncState();

            // begin a new download session
            IconCache.BeginGetIcon(currentStation, ar => {
                var img = IconCache.EndGetIcon(ar) ?? Resources.musicImage;

                // check if this download operation is still valid
                // if so, we assign the image normally, else we just return.
                if (((ValidAsyncState)ar.AsyncState).IsStillValid)
                    setToolStripImage(stationsListDropDown, img);

            }, _lastState);


            // checks the newly-selected station on the menu
            if (_selectedMenuItem != null) _selectedMenuItem.Checked = false;

            _selectedMenuItem = _menuItems.FirstOrDefault(m => (m.Tag as Station) == currentStation);
            if (_selectedMenuItem != null) _selectedMenuItem.Checked = true;
        }

        public void RefreshPlayState()
        {
            if (_radio.IsPlaying) {
                playToggleButton.Image = Resources.stopImage;
                playToggleMenu.Image = Resources.stopImage;
                playToggleMenu.Text = "Stop";

            } else {
                playToggleButton.Image = Resources.playImage;
                playToggleMenu.Image = Resources.playImage;
                playToggleMenu.Text = "Play";
            }
        }

        // cross-thread helper
        private void setToolStripImage(ToolStripItem item, Image img)
        {
            if (InvokeRequired)
                Invoke(new Action<ToolStripItem, Image>(setToolStripImage),
                    item, img);

            else {
                item.Image = img;
            }
        }

        // Event Handlers _______________________________________________________________

        // Radio Events

        private void radio_CurrentStationChanged(object sender, EventArgs e) { RefreshCurrentStation(); }

        private void radio_PlayStateChnged(object sender, EventArgs e) { RefreshPlayState(); }


        // UI Events

        private void stationItem_Click(object sender, EventArgs e)
        {
            var control = (ToolStripMenuItem)sender;

            var station = control.Tag as Station;
            _radio.CurrentStation = station;
        }

        private void quitButton_Click(object sender, EventArgs e)
        {
            if (_radio.PlayEngine.SupportHardStop)
                _radio.PlayEngine.HardStop();
            else
                _radio.Stop();

            this.Close();
        }

        private void quitButton_MouseEnter(object sender, EventArgs e)
        {
            // change into red image
            var target = (ToolStripItem)sender;
            target.Image = Resources.quitImage;
        }

        private void quitButton_MouseLeave(object sender, EventArgs e)
        {
            // change into grayed image
            var target = (ToolStripItem)sender;
            target.Image = Resources.quitGrayImage;
        }

        private void playToggleButton_Click(object sender, EventArgs e)
        {
            // issue proper play/stop
            if (!_radio.IsPlaying)
                _radio.Play();
            else
                _radio.Stop();

            RefreshPlayState();
        }

        private void editStationsMenu_Click(object sender, EventArgs e)
        {
            if (_editForm != null) return;

            _editForm = new editForm(_radio.Stations);
            _editForm.ShowDialog();

            _editForm.Dispose();
            _editForm = null;

            RefreshStationList();
        }


        // dragging support

        private void dragHolder_MouseDown(object sender, MouseEventArgs e)
        {
            _lastMouseLocation = e.Location;
            _isDragging = true;
        }

        private void dragHolder_MouseMove(object sender, MouseEventArgs e)
        {
            if (_isDragging)
                calculateDrag(e.Location);
        }

        private void dragHolder_MouseLeave(object sender, EventArgs e)
        {
            if (_isDragging)
                calculateDrag(toolStrip.PointToClient(MousePosition));
        }

        private void dragHolder_MouseUp(object sender, MouseEventArgs e) { _isDragging = false; }

        private void calculateDrag(Point newMouseLocation)
        {
            var dx = newMouseLocation.X - _lastMouseLocation.X;
            var dy = newMouseLocation.Y - _lastMouseLocation.Y;

            this.Location = new Point(
                this.Location.X + dx,
                this.Location.Y + dy);
        }


        // animation events

        private void animationTimer_Tick(object sender, EventArgs e)
        {
            if (this.Opacity != _targetOpactiy) {
                var delta = _targetOpactiy - this.Opacity;

                this.Opacity += Math.Abs(delta) < OpacityAnimationThreshold
                    ? delta
                    : delta * OpactiyAnimationFactor;

            } else {
                animationTimer.Stop();

            }
        }

        private void radioForm_MouseEnter(object sender, EventArgs e)
        {
            _targetOpactiy = ActiveOpacity;
            if (!_suppressAnimation) animationTimer.Start();
        }

        private void radioForm_MouseLeave(object sender, EventArgs e)
        {
            _targetOpactiy = InactiveOpacity;
            if (!_suppressAnimation) animationTimer.Start();
        }

        // workaround toolStrip not reporting mouse enter/leave to form first
        private void toolStrip_MouseLeave(object sender, EventArgs e) { this.OnMouseLeave(e); }
        private void toolStrip_MouseEnter(object sender, EventArgs e) { this.OnMouseEnter(e); }

        // when user move mouse over to select item on dropdownlist, the cursor
        // leaves the toolStrip triggering inactive animation which is not desired
        // since user is still using the form thus we need the suppress flag
        private void stationsListDropDown_DropDownOpened(object sender, EventArgs e) { _suppressAnimation = true; }

        private void stationsListDropDown_DropDownClosed(object sender, EventArgs e)
        {
            _suppressAnimation = false;

            // check if we should re-fire MouseLeave event
            // after releasing animation suppression.
            if (!this.Bounds.Contains(MousePosition))
                OnMouseLeave(e);
        }

        private void trayIcon_MouseClick(object sender, MouseEventArgs e)
        {
            // show/hide the form on single-left-click
            if (e.Button == MouseButtons.Left)
                this.Visible = !this.Visible;
        }
    }
}